﻿using System;
using System.IO;
using System.Collections.Generic;
using SlimDX;
using SlimDX.DXGI;
using SlimDX.Direct3D11;
using StillDesign.PhysX;
using Buffer = SlimDX.Direct3D11.Buffer;
using Device = SlimDX.Direct3D11.Device;


namespace WavefrontLoader
{

    public struct WFOFaceElement
    {
        public int P, T, N;
        public int Idx;

        public string PartialSignature
        {
            get { return P.ToString() + "/" + N.ToString(); }
        }

        public void GetIndexesFromText(string text)
        {
            string[] s = text.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            P = int.Parse(s[0]);
            T = int.Parse(s[1]);
            N = int.Parse(s[2]);
        }
    }

    public struct WFOFace
    {
        public WFOFaceElement E1, E2, E3;
    }

    public struct WFOVertex
    {
        public Vector3 Position;
        public Vector2 UV;
        public Vector3 Normal;
    }


    public class WFOGeometry
    {
        public const int VertexSize = 12 + 8 + 12;

        public WFOVertex[] Vertices;
        public int[] Indices;

        public Buffer VertexBuffer;
        public Buffer IndexBuffer;

        public int IndexCount { get; set; }
        public int VertexCount { get; set; }


        public WFOGeometry()
        {
            
        }


        public void DisposeAll()
        {
            VertexBuffer.Dispose();
            IndexBuffer.Dispose();
        }


        public void CreateBuffers(Device device)
        {
            DataStream stream;
            BufferDescription desc = new BufferDescription(VertexCount * VertexSize, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);

            stream = new DataStream(VertexCount * VertexSize, true, true);
            foreach (var v in Vertices)
                stream.Write<WFOVertex>(v);
            stream.Position = 0;
            //FileStream fs = File.Open("bikeVertexStream.str", FileMode.CreateNew);
            //stream.CopyTo(fs, VertexCount * VertexSize);
            //fs.Close();
            //stream.Position = 0;
            VertexBuffer = new Buffer(device, stream, desc);
            stream.Dispose();

            desc = new BufferDescription(IndexCount * 4, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
            stream = new DataStream(IndexCount * 4, true, true);
            foreach (var i in Indices)
                stream.Write<int>(i);
            stream.Position = 0;
            //fs = File.Open("bikeIndexStream.str", FileMode.CreateNew);
            //stream.CopyTo(fs, VertexCount * VertexSize);
            //fs.Close();
            //stream.Position = 0;
            IndexBuffer = new Buffer(device, stream, desc);
            stream.Dispose();
        }


        public static WFOGeometry CreateFromStreamFiles(Device device, string vertexStreamFileName, string indexStreamFileName)
        {
            FileStream fs;
            DataStream ds;
            BufferDescription desc;
            WFOGeometry g = new WFOGeometry();

            fs = File.Open(vertexStreamFileName, FileMode.Open);
            desc = new BufferDescription((int)fs.Length, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
            ds = new DataStream(fs.Length, true, true);
            fs.CopyTo(ds, (int)fs.Length);
            ds.Position = 0;
            g.VertexBuffer = new Buffer(device, ds, desc);
            g.VertexCount = (int)ds.Length / VertexSize;
            fs.Close();
            ds.Close();

            fs = File.Open(indexStreamFileName, FileMode.Open);
            desc = new BufferDescription((int)fs.Length, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
            ds = new DataStream(fs.Length, true, true); 
            fs.CopyTo(ds, (int)fs.Length);
            ds.Position = 0;
            g.IndexBuffer = new Buffer(device, ds, desc);
            g.IndexCount = (int)ds.Length / 4;
            fs.Close();
            ds.Close();

            return g;
        }


        public void Render(DeviceContext deviceContext, EffectTechnique technique, bool first)
        {
            if (first)
            {
                deviceContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(VertexBuffer, VertexSize, 0));
                deviceContext.InputAssembler.SetIndexBuffer(IndexBuffer, Format.R32_UInt, 0);
            }
            for (int i = 0; i < technique.Description.PassCount; i++)
            {
                technique.GetPassByIndex(i).Apply(deviceContext);
                deviceContext.DrawIndexed(IndexCount, 0, 0);
            }
        }

    }


    public class WaveFrontObjFile
    {
        private List<Vector3> FPositions;
        private List<Vector3> FNormals;
        private List<Vector2> FUVs;
        private List<WFOFace> FFaces;

        private List<string> Content;

        private List<WFOVertex> FVertices;
        private List<int> FIndices;

        public WFOGeometry Geometry;


        public WaveFrontObjFile(string filename)
        {
            Content = new List<string>();
            StreamReader reader = File.OpenText(filename);
            while (!reader.EndOfStream)
                Content.Add(reader.ReadLine());
            reader.Close();
            CleanupContent();
            ParseContent();
            Geometry = CreateVerticesIndices();
            Geometry.VertexCount = Geometry.Vertices.Length;
            Geometry.IndexCount = Geometry.Indices.Length;
            CleanUp();
        }


        private void CleanupContent()
        {
            List<string> Temp = new List<string>();
            foreach (string s in Content)
            {
                if (s == null) continue;
                if (s.Length < 1) continue;
                if ((s[0] == 'v') || (s[0] == 'f')) Temp.Add(s);
            }
            Content.Clear();
            Content = Temp;
        }


        private void ParseContent()
        {
            FPositions = new List<Vector3>();
            FNormals = new List<Vector3>();
            FUVs = new List<Vector2>();
            FFaces = new List<WFOFace>();

            //float minx = 999f, maxx = -999f, miny = 999f, maxy = -999f, minz = 999f, maxz = -999f;
            foreach (string s in Content)
            {
                string[] parts = s.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                float x, y, z = 0.0f;
                switch (parts[0])
                {
                    case "v":
                    case "vn":
                    case "vt":
                        z = parts[0] == "vt" ? 0.0f : float.Parse(parts[3].Replace('.', ',')); 
                        x = float.Parse(parts[1].Replace('.', ','));
                        y = float.Parse(parts[2].Replace('.', ','));
                        switch (parts[0])
                        {
                            case "v": FPositions.Add(new Vector3(x, y, z));
                                //minx = Math.Min(minx, x);
                                //maxx = Math.Max(maxx, x);
                                //miny = Math.Min(miny, y);
                                //maxy = Math.Max(maxy, y);
                                //minz = Math.Min(minz, z);
                                //maxz = Math.Max(maxz, z);
                                break;
                            case "vn": FNormals.Add(new Vector3(x, y, z)); break;
                            case "vt": FUVs.Add(new Vector2(x, y)); break;
                        }
                        break;
                    case "f":
                        WFOFace F = new WFOFace();
                        F.E1.GetIndexesFromText(parts[1]);
                        F.E2.GetIndexesFromText(parts[2]);
                        F.E3.GetIndexesFromText(parts[3]);
                        FFaces.Add(F);
                        break;
                }
            }
            //Console.WriteLine("minx {0} maxx {1}\n miny {2} maxy {3}\n minz {4} maxz {5}", minx, maxx, miny, maxy, minz, maxz);
        }


        private int CreateSingleElement(List<WFOVertex> Vert, List<string> Vsig, WFOFaceElement E)
        {
            if (Vsig.Contains(E.PartialSignature)) 
                return Vsig.IndexOf(E.PartialSignature);
            else
            {
                Vsig.Add(E.PartialSignature);
                WFOVertex V = new WFOVertex();
                V.Position = FPositions[E.P - 1];
                V.Position.X += 1.3f;
                V.Position.Z -= 0.03f;
                V.Normal = FNormals[E.N - 1];
                V.UV = FUVs[E.T - 1];
                Vert.Add(V);
                return Vsig.IndexOf(E.PartialSignature);
            }

        }


        private WFOGeometry CreateVerticesIndices()
        {
            WFOGeometry G = new WFOGeometry();
            FVertices = new List<WFOVertex>();
            List<string> Vsig = new List<string>();
            var F = FFaces.ToArray();
            for (int i = 0; i < F.Length; i++)
            {
                F[i].E1.Idx = CreateSingleElement(FVertices, Vsig, F[i].E1);
                F[i].E2.Idx = CreateSingleElement(FVertices, Vsig, F[i].E2);
                F[i].E3.Idx = CreateSingleElement(FVertices, Vsig, F[i].E3);
            }
            FIndices = new List<int>();
            foreach (var face in F)
            {
                FIndices.Add(face.E1.Idx);
                FIndices.Add(face.E2.Idx);
                FIndices.Add(face.E3.Idx);
            }
            G.Vertices = FVertices.ToArray();
            G.Indices = FIndices.ToArray();
            return G;
        }


        private void CleanUp()
        {
            Content.Clear();
            FPositions.Clear();
            FNormals.Clear();
            FUVs.Clear();
            FFaces.Clear();
            FVertices.Clear();
            FIndices.Clear();
        }

    }
}
